#pragma once
#include "Util.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>

#define TITLE_WEIGHT 15;
#define CONTENT_WEIGHT 2;

namespace Ns_Index
{
    // 文档数据
    struct DockInfo
    {
        std::string title;   // 文档标题
        std::string content; // 清洗后(去标签)的文档的内容
        std::string url;     // boost官方文档网址
        uint64_t doc_id;     // 文档id
    };

    // 倒排节点
    struct InvertedNode
    {
        std::string keyword; // 倒排索引关键字
        int doc_id;          // 文档id
        int weight;          // 权重，影响排序先后顺序
    };

    // 词频统计
    struct word_cnt
    {
        uint64_t title_cnt;   // 标题分词后词频统计
        uint64_t content_cnt; // 文档内容词频统计
    };

    class Index
    {
    public:
        // 倒排拉链
        typedef std::vector<InvertedNode> InvertedZipper;

    public:
        ~Index() {}

    public:
        // 正排索引：根据"文档id"找到"文档数据(DockInfo)"
        DockInfo *ForwardIndex(uint64_t doc_id)
        {
            // 判断doc_id是否超出数组范围
            if (doc_id >= Forward_Index.size())
            {
                std::cerr << "The id of index is out of range" << std::endl;
                return nullptr;
            }
            return &(Forward_Index[doc_id]);
        }

        // 倒排索引：根据"关键字"(分词后的词组)找到"一个或多个文档id(InvertedNode) ->> 倒排拉链"
        InvertedZipper *InvertedIndex(const std::string &keyword)
        {
            // 查找关键字是否存在
            auto iter = Inverted_Index.find(keyword);
            if (iter == Inverted_Index.end())
            {
                std::cerr << "The index key does not exist" << std::endl;
                return nullptr;
            }
            return &(iter->second);
        }

    public:
        // 构建索引，使用数据清洗后的内容，构建正排索引和倒排索引
        bool BulidIndex(const std::string &raw_path) // raw_path: "data/raw_html/raw.txt"
        {
            std::cout << "开始建立文档索引" << std::endl;
            
            // 读取raw_path路径下的raw.txt文件内容
            std::ifstream in(raw_path.c_str(), std::ios::in | std::ios::binary); // 只读和二进制状态打开
            if (!in.is_open())
            {
                std::cerr << "Fail to open raw.txt file" << std::endl;
                return false;
            }
            std::string raw_data;
            while (std::getline(in, raw_data)) // 循环按行读取清洗后的内容, 丢弃'\n'
            {
                // 构建正排索引
                DockInfo *doc = BulidForwardIndex(raw_data);
                if (doc == nullptr)
                {
                    std::cerr << "Failed to build forward index" << std::endl;
                    return false;
                }
                // 构建倒排索引
                BulidInvertedIndex(*doc);

                // For debug
                static size_t cnt = 0;
                cnt++;
                if (cnt % 50 == 0)
                {
                    std::cout << "已经建立好索引的文档数量: " << cnt << std::endl;
                }

            }
            std::cout << "建立文档索引完成!!!" << std::endl;
            return true;
        }

    private:
        DockInfo *BulidForwardIndex(const std::string &raw_data)
        {
            // 切割raw_data里面的字符串 -- 格式: "title\3content\3url\3" -> "title content url"
            std::vector<std::string> results;
            const std::string SPE = "\3";
            Ns_Util::StringUtil::CutString(raw_data, &results, SPE);
            if (results.size() != 3)
            {
                std::cerr << "Cutting failed, content missing" << std::endl;
                return nullptr;
            }
            // 填充DockInfo结构体 -- results-> [0]: title [1]: content [2]: url
            DockInfo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = Forward_Index.size(); // 文档id是从0下标让后增长的，一开始Forward_Index为空，大小就是0
            // 尾插到正排索引数据结构中
            Forward_Index.push_back(std::move(doc));
            return &(Forward_Index.back()); // 返回最新插入文件数据 -- 就是数组最后的数据
        }

        bool BulidInvertedIndex(const DockInfo &doc)
        {
            // 对doc的title标题进行分词
            std::vector<std::string> title_participle;
            std::unordered_map<std::string, word_cnt> wordfrequency_cnt; // 建立分词和词频的映射表
            Ns_Util::JiebaUtil::StringParticiple(doc.title, &title_participle); // 对title进行分词，词组放到title_participle
            // 词频统计
            for (auto str : title_participle)
            {
                // 搜索关键字不区分大小写，统一转换成小写
                boost::to_lower(str); // 统一转换小写
                wordfrequency_cnt[str].title_cnt++;
            }

            // 对doc的content(文档内容)进行分词
            std::vector<std::string> content_participle;
            Ns_Util::JiebaUtil::StringParticiple(doc.content, &content_participle); // 对content进行分词
            // 词频统计
            for (auto str : content_participle)
            {
                boost::to_lower(str);
                wordfrequency_cnt[str].content_cnt++;
            }

            // 建立倒排拉链(InvertedZipper) -- 建立一个或多个InvertedNode倒排节点
            for (auto &wfc : wordfrequency_cnt)
            {
                InvertedNode In;
                In.doc_id = doc.doc_id; // 文档id
                In.keyword = wfc.first; // 倒排索引关键字
                // 权重值，影响网页呈现文档的位置
                In.weight = wfc.second.title_cnt * TITLE_WEIGHT + wfc.second.content_cnt * CONTENT_WEIGHT;

                // 构建倒排拉链
                InvertedZipper &Iz = Inverted_Index[wfc.first]; // key值不存在就会自动创建
                Iz.push_back(std::move(In));
            }
            return true;
        }

    private:
        Index() {}
        Index(const Index &) = default;
        Index &operator=(const Index &) = default;
        static Index *instance;
        static std::mutex lock_;

    public:
        // 单例模式
        static Index *GetInstance()
        {
            // 防止不需要加锁的资源申请锁，导致阻塞住
            if (instance == nullptr)
            {
                Index::lock_.lock(); // 加锁
                if (instance == nullptr)
                    return new Index();
                Index::lock_.try_lock(); // 解锁
            }
            return instance;
        }

    private:
        // 正排索引数据结构，文档id天然是数组的下标(快速索引)
        std::vector<DockInfo> Forward_Index;
        // 倒排索引数据结构，一个关键字可能对应多个文档id(1:n -- n >= 1)，需要一个或多个倒排节点
        std::unordered_map<std::string, InvertedZipper> Inverted_Index;
    };
    Index *Index::instance = nullptr;
    std::mutex Index::lock_;
}